package dk.itu.mario.engine.JFugue;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.sound.midi.InvalidMidiDataException;

import org.jfugue.elements.Instrument;
import org.jfugue.parsers.MusicStringParser;
import org.jfugue.elements.Note;
import org.jfugue.Pattern;
import org.jfugue.PatternTransformer;
import org.jfugue.Player;
import org.jfugue.extras.DurationPatternTool;
import org.jfugue.extras.DurationPatternTransformer;
import org.jfugue.extras.GetPatternForVoiceTool;
import org.jfugue.extras.IntervalPatternTransformer;
import org.jfugue.extras.ReversePatternTransformer;

public class JFugueMidiImporter{

    private Player player;
    private File file;
    private MusicParserForMarioLevelGen parserMario;
    
    private Pattern mainPattern;
    private Pattern voicePattern;
    
    private int lastindex = -1;
    private int index;
    private int step = 1;
    private float direction;
    private Boolean isPlaying;
    
    private long totalTime;
    
    public JFugueMidiImporter(File file) {
        this.player = new Player();
        this.file = file;
    }

    public JFugueMidiImporter(JFugueMidiImporter midiImport) {
		this.player = new Player();
    	this.file = midiImport.file;
		this.mainPattern = new Pattern(midiImport.mainPattern);
		this.voicePattern = new Pattern(midiImport.voicePattern);
		this.parserMario = new MusicParserForMarioLevelGen(mainPattern);
		this.totalTime = getTotalTime();
	}

	/**
	 * @return the player
	 */
	public Player getPlayer() {
		return player;
	}

	/**
	 * @param player the player to set
	 */
	public void setPlayer(Player player) {
		this.player = player;
	}

	/** Loads a MIDI file, and converts the MIDI into a JFugue Pattern. */
    public Pattern getPattern() {
        Pattern pattern = null;
        try {
            pattern = (Pattern) (player.loadMidi(file));
        } catch (InvalidMidiDataException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return pattern;
    }

    /**
     * Using the GetPatternForVoiceTool, isolate a single voice (or channel) of the
     * Pattern and return it.
     */
    public Pattern getPatternForVoice(Pattern pattern, int voice) {
        GetPatternForVoiceTool tool = new GetPatternForVoiceTool(voice);

        MusicStringParser parser = new MusicStringParser();
        parser.addParserListener(tool);
//        System.out.println("Voice " + voice + ", Pattern " + pattern.getMusicString());
        parser.parse(pattern);
        Pattern voicePattern = (Pattern) tool.getPattern();
        
        return voicePattern;
    }

    /** Plays a Pattern - this is a pass-through method to JFugue's Player */
    public void play(Pattern pattern) {
        player.play(pattern);
    }
    
    public void playPart(int index, int step, float direction) {
    	Pattern newPattern = null;
    	
    	
    	//CHECK THIS OUT WHEN PASSING INFO!!!
    	
//    	totalmidilength = 100 seconds
//    	speed = 5 level units / second
//    	totallevellength = 500 level units
//    	mariox = 300 level units
    	
//    	totalindices = 
//    	index = mariox / totallevellength * totalindices
//    	step = totallevellength / totalmidilength
    	
    	newPattern = getSubPattern(index, step, direction);
    	
    	if(direction != 0)
    	{
//    		System.out.println(newPattern.getMusicString());
    		player.play(newPattern);
    	}
    }
    
    public Pattern getSubPattern(int index, int step, float direction)
    {
    	if(direction >= 0)
    	{
//    		newPattern = (Pattern)mainPattern.getSubPattern(index * step, (index + 1) * step);
    		return new Pattern(parserMario.getMusicString(index, index + step));
    	}
    	else if(direction < 0)
    	{
//    		newPattern = reversePattern((Pattern)mainPattern.getSubPattern((index - 1) * step, index * step));
    		return reversePattern(new Pattern(parserMario.getMusicString(index - 1, index - 1 + step)));
    	}
    	
    	else return new Pattern();
    }

    /**
     * Using the InvertPatternTransformer, invert the given pattern.
     */
    public Pattern invertPattern(Pattern pattern) {
        InvertPatternTransformer ipt = new InvertPatternTransformer(MusicStringParser.getNote("C5"));
        Pattern invertPattern = (Pattern) ipt.transform(pattern);
        return invertPattern;
    }

    /**
     * Using the ReversePatternTransformer, reverse the given pattern.
     * "C D E" becomes "E D C"
     */
    public Pattern reversePattern(Pattern pattern) {
        ReversePatternTransformer rpt = new ReversePatternTransformer();
        Pattern reversePattern = (Pattern) rpt.transform(pattern);
        return reversePattern;
    }

    /**
     * Causes the duration of each note in the Pattern to be lengthened
     * by the provided factor.
     */
    public Pattern stretchPattern(Pattern pattern, double stretch) {
        DurationPatternTransformer dpt = new DurationPatternTransformer(stretch);
        Pattern stretchedPattern = (Pattern) dpt.transform(pattern);
        return stretchedPattern;
    }

    /**
     * Changes the note values of each note in the Pattern - this causes
     * the entire Pattern to be played with higher or lower pitches.   
     */
    public Pattern changeInterval(Pattern pattern, int delta) {
        IntervalPatternTransformer it = new IntervalPatternTransformer(delta);
        Pattern intervalPattern = (Pattern) it.transform(pattern);
        return intervalPattern;
    }

    public Pattern cleanInstrument(Pattern pattern) {
        PatternTransformer t = new PatternTransformer() {

            @Override
            public void instrumentEvent(Instrument instrument) {
                // Do nothing
            }
        };
        Pattern cleanedPattern = (Pattern) t.transform(pattern);
        return cleanedPattern;
    }
    
    public void importMIDI()
    {
    	mainPattern = getPattern();
//        System.out.println("From midi: " + mainPattern.getMusicString());
        voicePattern = getPatternForVoice(mainPattern, 0);
        parserMario = new MusicParserForMarioLevelGen(mainPattern);
        totalTime = parserMario.getTemporalUnits().get(parserMario.getTemporalUnits().size() - 1).getTime();
        
        index = 0;
        step = 10;
        
        try
        {
//        	play(voicePattern);
        }
        catch(Exception e)
        {
        	e.printStackTrace();
        }
    }

    //Call this to import the MIDI file. Input filename in MIDIs folder.
    public static void ImportMIDI(String fileName) {
    	JFugueMidiImporter mix = new JFugueMidiImporter(new File("MIDIs/"+ fileName));
        mix.mainPattern = mix.getPattern();
//        System.out.println("From midi: " + mix.mainPattern.getMusicString());
        mix.voicePattern = mix.getPatternForVoice(mix.mainPattern, 9);
        
//        Pattern voicePattern = pattern;
        
        mix.parserMario = new MusicParserForMarioLevelGen(mix.mainPattern);
        mix.totalTime = mix.parserMario.getTemporalUnits().get(mix.parserMario.getTemporalUnits().size() - 1).getTime();
        
//        System.out.println("From single voice: " + voicePattern.getMusicString());

//        Pattern cleanInstrumentPattern = mix.cleanInstrument(voicePattern);
//        Pattern cleanInstrumentPattern = mix.cleanInstrument(pattern);
        
        //System.out.println("Clean MIDI: " + cleanInstrumentPattern.getMusicString());
        
//        Pattern stretchedPattern = mix.stretchPattern(voicePattern, 0.25);
//        System.out.println("Stretched MIDI: " + stretchedPattern.getMusicString());
        
//        cleanInstrumentPattern.setProperty("Tempo", "500");
        
//        Pattern invertedPattern = mix.invertPattern(cleanInstrumentPattern);
//        System.out.println("After inversion: " + invertedPattern);
//
//        Pattern reversedPattern = mix.reversePattern(invertedPattern);
//        System.out.println("After reversal: " + reversedPattern);
////        mix.play(reversedPattern);
//
////        reversedPattern = mix.stretchPattern(reversedPattern, 0.3);
//        reversedPattern = mix.changeInterval(reversedPattern, +16);
////        mix.play(reversedPattern);
//        System.out.println(reversedPattern);
//
//        // Add pattern to itself, reversed again
//        Pattern reversedAgain = mix.reversePattern(reversedPattern);
//        reversedPattern.add(reversedAgain);
//
//        // Repeat it
//        reversedPattern.repeat(4);
//
//        Pattern fugue = new Pattern();
//        fugue.add("V0 I[Piano]");
//        fugue.add(reversedPattern);
//        fugue.add("V1 I[Synth_strings_1] R/0.637353");
//        fugue.add(mix.changeInterval(reversedPattern, -12));
//        fugue.add("V2 I[Blown_Bottle] R/0.637353 R/0.566652");
//        fugue.add(mix.changeInterval(reversedPattern, -17));
//        System.out.println(fugue);

//        mix.play(fugue);

        try {
            //fugue.savePattern(new File("fugue.jfugue"));
        	
//        	DurationPatternTool dpt = new DurationPatternTool();
//        	System.out.println("Duration of pattern1 is " + dpt.execute(voicePattern));
        	
//        	Pattern newPattern = new Pattern("T3096" + voicePattern.getMusicString().substring(11));
//        	System.out.println("After Tempo change: " + newPattern.getMusicString());
//        	System.out.println("Duration of pattern2 is " + dpt.execute(newPattern));
//        	System.out.println(mix.parserMario.toString());
//        	MusicParserForMarioLevelGen newList = new MusicParserForMarioLevelGen(mix.parserMario.getNoteListFiltered(38));
//        	
//        	System.out.println(newList.toString());
        	
//        	mix.play(voicePattern);
        	
//        	voicePattern.savePattern(new File("fugue.jfugue"));
        	
//        	mix.play(cleanInstrumentPattern);
        	//cleanInstrumentPattern.savePattern(new File("fugue.jfugue"));
        	
//        	mix.play(pattern);
//        	pattern.savePattern(new File("fugue.jfugue"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public ArrayList<LayeredNote> getNoteListForVoice(int voice)
    {
    	voicePattern = getPatternForVoice(mainPattern, voice);
    	
    	parserMario = new MusicParserForMarioLevelGen(voicePattern);
    	
    	return parserMario.getTemporalUnits();
    }

	/**
	 * @return the parserMario
	 */
	public MusicParserForMarioLevelGen getParserMario() {
		return parserMario;
	}

	/**
	 * @param parserMario the parserMario to set
	 */
	public void setParserMario(MusicParserForMarioLevelGen parserMario) {
		this.parserMario = parserMario;
	}
	
	

    /**
	 * @return the totalTime
	 */
	public long getTotalTime() {
		return totalTime;
	}

	/**
	 * @param totalTime the totalTime to set
	 */
	public void setTotalTime(long totalTime) {
		this.totalTime = totalTime;
	}

//	public static void main(String[] args) {
//        ImportMIDI("Carcass- Incarnated Solvent Abuse.mid");
//    }

	/**
	 * @return the index
	 */
	public int getIndex() {
		return index;
	}

	/**
	 * @param index the index to set
	 */
	public void setIndex(int index) {
		this.index = index;
	}

	/**
	 * @return the step
	 */
	public int getStep() {
		return step;
	}

	/**
	 * @param step the step to set
	 */
	public void setStep(int step) {
		this.step = step;
	}

	/**
	 * @return the direction
	 */
	public float getDirection() {
		return direction;
	}

	/**
	 * @param xa the direction to set
	 */
	public void setDirection(float xa) {
		this.direction = xa;
	}

	/**
	 * @return the isPlaying
	 */
	public Boolean getIsPlaying() {
		return isPlaying;
	}

	/**
	 * @param isPlaying the isPlaying to set
	 */
	public void setIsPlaying(Boolean isPlaying) {
		this.isPlaying = isPlaying;
		
//		if(isPlaying)
//		{
//			playPart();
//		}
	}
//	
//	public synchronized void playPart()
//	{
//		new Thread(new Runnable() {
//			public void run()
//			{
//				try 
//				{
////					if(index != lastindex & isPlaying)
//					{
////						lastindex = index;
////						while(isPlaying)
//						{
////							playPart(index, parserMario.getTemporalUnits().size() - 2, direction);
//							player.play(getSubPattern(0, parserMario.getTemporalUnits().size() - 2, direction));
////							index += 2;
////							Thread.sleep(500);
//						}
//					}
//				}
//				catch (Exception e)
//				{
//					System.err.println(e.getMessage());
//				}
//			}
//	    }).start();
//	}
}


//***Sample PatternTransformer***
class InvertPatternTransformer extends PatternTransformer {

    private byte fulcrumNoteValue;

    public InvertPatternTransformer(Note note) {
        this.fulcrumNoteValue = note.getValue();
    }

    /** Transforms the given note */
    @Override
    public void noteEvent(Note note) {
        doNoteEvent(note);
    }

    /** Transforms the given note */
    @Override
    public void sequentialNoteEvent(Note note) {
        doNoteEvent(note);
    }

    /** Transforms the given note */
    @Override
    public void parallelNoteEvent(Note note) {
        doNoteEvent(note);
    }
    
    

    private void doNoteEvent(Note note) {
        byte noteValue = note.getValue();

        if (noteValue > fulcrumNoteValue) {
            note.setValue((byte) (fulcrumNoteValue - (noteValue - fulcrumNoteValue)));
            getReturnPattern().addElement(note);
        } else if (noteValue < fulcrumNoteValue) {
            note.setValue((byte) (fulcrumNoteValue - (fulcrumNoteValue - noteValue)));
            getReturnPattern().addElement(note);
        } else {
            //  No change in note value
            getReturnPattern().addElement(note);
        }
    }
}